#include "Spark/Net/SocketAddr.h"
#include "Spark/Utils/Logger.h"
#include <stdexcept>
#include <arpa/inet.h>

static const in_addr_t kIPAny = INADDR_ANY;
static const in_addr_t kLoopbackIP = INADDR_LOOPBACK;
static const sa_family_t kIPv4Family = AF_INET;
static const sa_family_t kIPv6Family = AF_INET6;
static_assert(INADDR_ANY == 0x0, "INADDR_ANY is not 0x0");
static_assert(INADDR_LOOPBACK == 0x7f000001,
              "INADDR_LOOPBACK is not 0x7f000001");

SocketAddr::SocketAddr(const char* ipstr, in_port_t port, bool ipv6) : addr_{}
{
    if (ipv6)
    {
        addr_.sin6.sin6_family = kIPv6Family;
        addr_.sin6.sin6_port = htons(port);
        if (::inet_pton(kIPv6Family, ipstr, &addr_.sin6.sin6_addr) < 0)
            throw std::invalid_argument(std::string(ipstr) +
                                        " is not a valid IP for IPv6");
    }
    else
    {
        addr_.sin.sin_family = kIPv4Family;
        addr_.sin.sin_port = htons(port);
        if (::inet_pton(kIPv4Family, ipstr, &addr_.sin.sin_addr) < 0)
            throw std::invalid_argument(std::string(ipstr) +
                                        " is not a valid IP for IPv4");
    }
}

SocketAddr SocketAddr::loopbackAddr(in_port_t port,
                                    bool loopbackOnly,
                                    bool ipv6)
{
    sockaddr sa;
    if (ipv6)
    {
        sockaddr_in6* sin6 = reinterpret_cast<sockaddr_in6*>(&sa);
        sin6->sin6_family = kIPv6Family;
        sin6->sin6_addr = loopbackOnly ? in6addr_loopback : in6addr_any;
        sin6->sin6_port = htons(port);
    }
    else
    {
        sockaddr_in* sin = reinterpret_cast<sockaddr_in*>(&sa);
        sin->sin_family = kIPv4Family;
        sin->sin_addr.s_addr = loopbackOnly ? kLoopbackIP : kIPAny;
        sin->sin_port = htons(port);
    }
    return SocketAddr(sa);
}

SocketAddr::SocketAddr(const sockaddr& sa) : addr_{.sa = sa}
{
}

SocketAddr::SocketAddr(const sockaddr_in& sin) : addr_{.sin = sin}
{
}

SocketAddr::SocketAddr(const sockaddr_in6& sin6) : addr_{.sin6 = sin6}
{
}

SocketAddr SocketAddr::getLocalAddr(int sockfd)
{
    sockaddr local{};
    socklen_t socklen = kIn6AddrLen;
    if (::getsockname(sockfd, &local, &socklen) < 0)
        LOG_SYSERR << "SocketAddr::getLocalAddr";
    return SocketAddr(local);
}

SocketAddr SocketAddr::getPeerAddr(int sockfd)
{
    sockaddr peer{};
    socklen_t socklen = kIn6AddrLen;
    if (::getpeername(sockfd, &peer, &socklen) < 0)
        LOG_SYSERR << "SocketAddr::getPeerAddr";
    return SocketAddr(peer);
}

inline in_addr SocketAddr::ipv4() const
{
    assert(addr_.sa.sa_family == kIPv4Family);
    return addr_.sin.sin_addr;
}

inline in6_addr SocketAddr::ipv6() const
{
    assert(addr_.sa.sa_family == kIPv6Family);
    return addr_.sin6.sin6_addr;
}

void SocketAddr::setAddr(const sockaddr* addr, socklen_t socklen)
{
    memcpy(&addr_.sa, addr, socklen);
}

std::string SocketAddr::toString() const
{
    char buf[INET6_ADDRSTRLEN];
    if (addr_.sa.sa_family == kIPv6Family)
    {
        ::inet_ntop(kIPv6Family, &addr_.sin6.sin6_addr, buf, sizeof(buf));
    }
    else
    {
        ::inet_ntop(kIPv4Family, &addr_.sin.sin_addr, buf, sizeof(buf));
    }
    return buf;
}

std::string SocketAddr::toStringPort() const
{
    char buf[10];
    snprintf(buf, sizeof(buf), ":%hu", ntohs(addr_.sin.sin_port));
    return toString() + buf;
}

bool SocketAddr::isIntraNetIP() const
{
    if (addr_.sin.sin_family == kIPv4Family)
    {
        auto ip_addr = ipv4().s_addr;
        if ((ip_addr >= 0x0A000000 && ip_addr <= 0x0AFFFFFF) ||
            (ip_addr >= 0xAC100000 && ip_addr <= 0xAC1FFFFF) ||
            (ip_addr >= 0xC0A80000 && ip_addr <= 0xC0A8FFFF) ||
            ip_addr == 0x7f000001)
        {
            return true;
        }
    }
    else
    {
        auto addr6 = ipv6();
        uint32_t* addrP = addr6.__in6_u.__u6_addr32;
        // Loopback ip
        if (*addrP == 0 && *(addrP + 1) == 0 && *(addrP + 2) == 0 &&
            ntohl(*(addrP + 3)) == 1)
            return true;
        // Privated ip is prefixed by FEC0::/10 or FE80::/10, need testing
        auto i32 = (ntohl(*addrP) & 0xffc00000);
        if (i32 == 0xfec00000 || i32 == 0xfe800000)
            return true;
        if (*addrP == 0 && *(addrP + 1) == 0 && ntohl(*(addrP + 2)) == 0xffff)
        {
            // the IPv6 version of an IPv4 IP address
            uint32_t ip_addr = ntohl(*(addrP + 3));
            if ((ip_addr >= 0x0A000000 && ip_addr <= 0x0AFFFFFF) ||
                (ip_addr >= 0xAC100000 && ip_addr <= 0xAC1FFFFF) ||
                (ip_addr >= 0xC0A80000 && ip_addr <= 0xC0A8FFFF) ||
                ip_addr == 0x7f000001)
            {
                return true;
            }
        }
    }
    return false;
}
